diff --git a/swh/web/browse/snapshot_context.py b/swh/web/browse/snapshot_context.py --- a/swh/web/browse/snapshot_context.py +++ b/swh/web/browse/snapshot_context.py @@ -430,6 +430,12 @@ origin_visits_url = None if origin_url: + + if visit_id is not None: + query_params["visit_id"] = visit_id + elif snapshot_id is not None: + query_params["snapshot"] = snapshot_id + origin_info = service.lookup_origin({"url": origin_url}) visit_info = get_origin_visit(origin_info, timestamp, visit_id, snapshot_id) @@ -455,10 +461,9 @@ query_params["origin_url"] = origin_info["url"] - origin_visits_url = reverse("browse-origin-visits", query_params=query_params) - - if visit_id is not None: - query_params["visit_id"] = visit_id + origin_visits_url = reverse( + "browse-origin-visits", query_params={"origin_url": origin_info["url"]} + ) if timestamp is not None: query_params["timestamp"] = format_utc_iso_date( @@ -729,6 +734,7 @@ history_url = None if snapshot_id != _empty_snapshot_id: + query_params.pop("path", None) history_url = reverse( browse_view_name, url_args=url_args, query_params=query_params ) @@ -1223,18 +1229,16 @@ return handle_view_exception(request, exc) for branch in displayed_branches: - if snapshot_id: - revision_url = reverse( - "browse-revision", - url_args={"sha1_git": branch["revision"]}, - query_params={"snapshot_id": snapshot_id}, - ) - else: - revision_url = reverse( - "browse-revision", - url_args={"sha1_git": branch["revision"]}, - query_params={"origin_url": origin_info["url"]}, - ) + rev_query_params = {} + if origin_info: + rev_query_params["origin_url"] = origin_info["url"] + + revision_url = reverse( + "browse-revision", + url_args={"sha1_git": branch["revision"]}, + query_params=query_params, + ) + query_params["branch"] = branch["name"] directory_url = reverse( browse_view_name, url_args=url_args, query_params=query_params @@ -1334,10 +1338,10 @@ return handle_view_exception(request, exc) for release in displayed_releases: - if snapshot_id: - query_params_tgt = {"snapshot_id": snapshot_id} - else: - query_params_tgt = {"origin_url": origin_info["url"]} + query_params_tgt = {"snapshot": snapshot_id} + if origin_info: + query_params_tgt["origin_url"] = origin_info["url"] + release_url = reverse( "browse-release", url_args={"sha1_git": release["id"]}, diff --git a/swh/web/browse/utils.py b/swh/web/browse/utils.py --- a/swh/web/browse/utils.py +++ b/swh/web/browse/utils.py @@ -315,16 +315,28 @@ def _snapshot_context_query_params(snapshot_context): - query_params = None + query_params = {} + if not snapshot_context: + return query_params if snapshot_context and snapshot_context["origin_info"]: origin_info = snapshot_context["origin_info"] + snp_query_params = snapshot_context["query_params"] query_params = {"origin_url": origin_info["url"]} - if "timestamp" in snapshot_context["query_params"]: - query_params["timestamp"] = snapshot_context["query_params"]["timestamp"] - if "visit_id" in snapshot_context["query_params"]: - query_params["visit_id"] = snapshot_context["query_params"]["visit_id"] + if "timestamp" in snp_query_params: + query_params["timestamp"] = snp_query_params["timestamp"] + if "visit_id" in snp_query_params: + query_params["visit_id"] = snp_query_params["visit_id"] + if "snapshot" in snp_query_params and "visit_id" not in query_params: + query_params["snapshot"] = snp_query_params["snapshot"] elif snapshot_context: - query_params = {"snapshot_id": snapshot_context["snapshot_id"]} + query_params = {"snapshot": snapshot_context["snapshot_id"]} + + if snapshot_context["release"]: + query_params["release"] = snapshot_context["release"] + elif snapshot_context["branch"] and snapshot_context["branch"] != "HEAD": + query_params["branch"] = snapshot_context["branch"] + elif snapshot_context["revision_id"]: + query_params["revision"] = snapshot_context["revision_id"] return query_params @@ -342,6 +354,7 @@ """ query_params = _snapshot_context_query_params(snapshot_context) + query_params.pop("revision", None) return reverse( "browse-revision", url_args={"sha1_git": revision_id}, query_params=query_params @@ -504,17 +517,16 @@ Returns: The revision log view URL """ - query_params = {"revision": revision_id} + query_params = {} + if snapshot_context: + query_params = _snapshot_context_query_params(snapshot_context) + + query_params["revision"] = revision_id if snapshot_context and snapshot_context["origin_info"]: - origin_info = snapshot_context["origin_info"] - query_params["origin_url"] = origin_info["url"] - if "timestamp" in snapshot_context["query_params"]: - query_params["timestamp"] = snapshot_context["query_params"]["timestamp"] - if "visit_id" in snapshot_context["query_params"]: - query_params["visit_id"] = snapshot_context["query_params"]["visit_id"] revision_log_url = reverse("browse-origin-log", query_params=query_params) elif snapshot_context: url_args = {"snapshot_id": snapshot_context["snapshot_id"]} + del query_params["snapshot"] revision_log_url = reverse( "browse-snapshot-log", url_args=url_args, query_params=query_params ) diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py --- a/swh/web/browse/views/content.py +++ b/swh/web/browse/views/content.py @@ -194,37 +194,44 @@ algo, checksum = query.parse_hash(query_string) checksum = hash_to_hex(checksum) content_data = request_content(query_string, raise_if_unavailable=False) - origin_url = request.GET.get("origin_url", None) - selected_language = request.GET.get("language", None) - + origin_url = request.GET.get("origin_url") + selected_language = request.GET.get("language") if not origin_url: - origin_url = request.GET.get("origin", None) + origin_url = request.GET.get("origin") + snapshot_id = request.GET.get("snapshot") + path = request.GET.get("path") snapshot_context = None - if origin_url: + if origin_url is not None or snapshot_id is not None: try: - snapshot_context = get_snapshot_context(origin_url=origin_url) - except NotFoundExc: - raw_cnt_url = reverse( - "browse-content", url_args={"query_string": query_string} - ) - error_message = ( - "The Software Heritage archive has a content " - "with the hash you provided but the origin " - "mentioned in your request appears broken: %s. " - "Please check the URL and try again.\n\n" - "Nevertheless, you can still browse the content " - "without origin information: %s" - % (gen_link(origin_url), gen_link(raw_cnt_url)) + snapshot_context = get_snapshot_context( + origin_url=origin_url, + snapshot_id=snapshot_id, + branch_name=request.GET.get("branch"), + release_name=request.GET.get("release"), + revision_id=request.GET.get("revision"), + path=path, + browse_context=CONTENT, ) - - raise NotFoundExc(error_message) - if snapshot_context: - snapshot_context["visit_info"] = None + except NotFoundExc as e: + if str(e).startswith("Origin"): + raw_cnt_url = reverse( + "browse-content", url_args={"query_string": query_string} + ) + error_message = ( + "The Software Heritage archive has a content " + "with the hash you provided but the origin " + "mentioned in your request appears broken: %s. " + "Please check the URL and try again.\n\n" + "Nevertheless, you can still browse the content " + "without origin information: %s" + % (gen_link(origin_url), gen_link(raw_cnt_url)) + ) + raise NotFoundExc(error_message) + else: + raise e except Exception as exc: return handle_view_exception(request, exc) - path = request.GET.get("path", None) - content = None language = None mimetype = None @@ -245,24 +252,28 @@ if mimetype and "text/" in mimetype: available_languages = highlightjs.get_supported_languages() - root_dir = None filename = None path_info = None directory_id = None directory_url = None - query_params = {"origin_url": origin_url} + root_dir = None + if snapshot_context: + root_dir = snapshot_context.get("root_directory") + + query_params = snapshot_context["query_params"] if snapshot_context else {} breadcrumbs = [] if path: split_path = path.split("/") - root_dir = split_path[0] + root_dir = root_dir or split_path[0] filename = split_path[-1] if root_dir != path: path = path.replace(root_dir + "/", "") path = path[: -len(filename)] path_info = gen_path_info(path) + query_params.pop("path", None) dir_url = reverse( "browse-directory", url_args={"sha1_git": root_dir}, diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py --- a/swh/web/browse/views/directory.py +++ b/swh/web/browse/views/directory.py @@ -23,7 +23,7 @@ from swh.web.common.exc import handle_view_exception, NotFoundExc from swh.web.common.identifiers import get_swhids_info from swh.web.common.typing import DirectoryMetadata, SWHObjectInfo -from swh.web.common.utils import reverse, gen_path_info +from swh.web.common.utils import reverse, gen_path_info, swh_object_icons def _directory_browse(request, sha1_git, path=None): @@ -34,36 +34,44 @@ sha1_git = dir_info["target"] dirs, files = get_directory_entries(sha1_git) - origin_url = request.GET.get("origin_url", None) + origin_url = request.GET.get("origin_url") if not origin_url: - origin_url = request.GET.get("origin", None) + origin_url = request.GET.get("origin") + snapshot_id = request.GET.get("snapshot") snapshot_context = None - if origin_url: + if origin_url is not None or snapshot_id is not None: try: - snapshot_context = get_snapshot_context(origin_url=origin_url) - except NotFoundExc: - raw_dir_url = reverse( - "browse-directory", url_args={"sha1_git": sha1_git} + snapshot_context = get_snapshot_context( + snapshot_id=snapshot_id, + origin_url=origin_url, + branch_name=request.GET.get("branch"), + release_name=request.GET.get("release"), + revision_id=request.GET.get("revision"), + path=path, ) - error_message = ( - "The Software Heritage archive has a directory " - "with the hash you provided but the origin " - "mentioned in your request appears broken: %s. " - "Please check the URL and try again.\n\n" - "Nevertheless, you can still browse the directory " - "without origin information: %s" - % (gen_link(origin_url), gen_link(raw_dir_url)) - ) - - raise NotFoundExc(error_message) - if snapshot_context: - snapshot_context["visit_info"] = None + except NotFoundExc as e: + if str(e).startswith("Origin"): + raw_dir_url = reverse( + "browse-directory", url_args={"sha1_git": sha1_git} + ) + error_message = ( + "The Software Heritage archive has a directory " + "with the hash you provided but the origin " + "mentioned in your request appears broken: %s. " + "Please check the URL and try again.\n\n" + "Nevertheless, you can still browse the directory " + "without origin information: %s" + % (gen_link(origin_url), gen_link(raw_dir_url)) + ) + raise NotFoundExc(error_message) + else: + raise e except Exception as exc: return handle_view_exception(request, exc) path_info = gen_path_info(path) - query_params = {"origin_url": origin_url} + query_params = snapshot_context["query_params"] if snapshot_context else {} breadcrumbs = [] breadcrumbs.append( @@ -115,7 +123,7 @@ url_args={"query_string": query_string}, query_params={ "path": root_sha1_git + "/" + path + f["name"], - "origin_url": origin_url, + **query_params, }, ) if f["length"] is not None: @@ -159,6 +167,19 @@ dir_path = "/".join([bc["name"] for bc in breadcrumbs]) + "/" heading += " - %s" % dir_path + top_right_link = None + if snapshot_context is not None and not snapshot_context["is_empty"]: + history_url = reverse( + "browse-revision-log", + url_args={"sha1_git": snapshot_context["revision_id"]}, + query_params=query_params, + ) + top_right_link = { + "url": history_url, + "icon": swh_object_icons["revisions history"], + "text": "History", + } + return render( request, "browse/directory.html", @@ -170,7 +191,7 @@ "dirs": dirs, "files": files, "breadcrumbs": breadcrumbs, - "top_right_link": None, + "top_right_link": top_right_link, "readme_name": readme_name, "readme_url": readme_url, "readme_html": readme_html, diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -33,6 +33,7 @@ return browse_snapshot_directory( request, origin_url=request.GET.get("origin_url"), + snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), path=request.GET.get("path"), ) @@ -54,7 +55,11 @@ :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/directory/[(path)/]` """ return browse_snapshot_directory( - request, origin_url=origin_url, timestamp=timestamp, path=path + request, + origin_url=origin_url, + snapshot_id=request.GET.get("snapshot"), + timestamp=timestamp, + path=path, ) @@ -71,6 +76,7 @@ return browse_snapshot_content( request, origin_url=request.GET.get("origin_url"), + snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), path=request.GET.get("path"), selected_language=request.GET.get("language"), @@ -95,6 +101,7 @@ return browse_snapshot_content( request, origin_url=origin_url, + snapshot_id=request.GET.get("snapshot"), timestamp=timestamp, path=path, selected_language=request.GET.get("language"), @@ -113,6 +120,7 @@ return browse_snapshot_log( request, origin_url=request.GET.get("origin_url"), + snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), ) @@ -131,7 +139,12 @@ :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/log/` """ - return browse_snapshot_log(request, origin_url=origin_url, timestamp=timestamp) + return browse_snapshot_log( + request, + origin_url=origin_url, + snapshot_id=request.GET.get("snapshot"), + timestamp=timestamp, + ) @browse_route( @@ -147,6 +160,7 @@ return browse_snapshot_branches( request, origin_url=request.GET.get("origin_url"), + snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), ) @@ -165,7 +179,12 @@ :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/branches/` """ - return browse_snapshot_branches(request, origin_url=origin_url, timestamp=timestamp) + return browse_snapshot_branches( + request, + origin_url=origin_url, + snapshot_id=request.GET.get("snapshot"), + timestamp=timestamp, + ) @browse_route( @@ -181,6 +200,7 @@ return browse_snapshot_releases( request, origin_url=request.GET.get("origin_url"), + snapshot_id=request.GET.get("snapshot"), timestamp=request.GET.get("timestamp"), ) @@ -199,7 +219,12 @@ :http:get:`/browse/origin/(origin_url)/visit/(timestamp)/releases/` """ - return browse_snapshot_releases(request, origin_url=origin_url, timestamp=timestamp) + return browse_snapshot_releases( + request, + origin_url=origin_url, + snapshot_id=request.GET.get("snapshot"), + timestamp=timestamp, + ) def _origin_visits_browse(request, origin_url): diff --git a/swh/web/browse/views/release.py b/swh/web/browse/views/release.py --- a/swh/web/browse/views/release.py +++ b/swh/web/browse/views/release.py @@ -41,18 +41,20 @@ release = service.lookup_release(sha1_git) snapshot_context = {} origin_info = None - snapshot_id = request.GET.get("snapshot_id", None) - origin_url = request.GET.get("origin_url", None) + snapshot_id = request.GET.get("snapshot_id") + if not snapshot_id: + snapshot_id = request.GET.get("snapshot") + origin_url = request.GET.get("origin_url") if not origin_url: - origin_url = request.GET.get("origin", None) - timestamp = request.GET.get("timestamp", None) - visit_id = request.GET.get("visit_id", None) + origin_url = request.GET.get("origin") + timestamp = request.GET.get("timestamp") + visit_id = request.GET.get("visit_id") if origin_url: try: snapshot_context = get_snapshot_context( snapshot_id, origin_url, timestamp, visit_id ) - except NotFoundExc: + except NotFoundExc as e: raw_rel_url = reverse("browse-release", url_args={"sha1_git": sha1_git}) error_message = ( "The Software Heritage archive has a release " @@ -63,8 +65,10 @@ "without origin information: %s" % (gen_link(origin_url), gen_link(raw_rel_url)) ) - - raise NotFoundExc(error_message) + if str(e).startswith("Origin"): + raise NotFoundExc(error_message) + else: + raise e origin_info = snapshot_context["origin_info"] elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) @@ -174,6 +178,7 @@ query_params={ "origin_url": origin_info["url"], "release": release["name"], + "snapshot": snapshot_id, }, ) elif snapshot_id: diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -208,6 +208,19 @@ The url that points to it is :http:get:`/browse/revision/(sha1_git)/log/` """ try: + origin_url = request.GET.get("origin_url") + snapshot_id = request.GET.get("snapshot") + snapshot_context = None + if origin_url or snapshot_id: + snapshot_context = get_snapshot_context( + snapshot_id=snapshot_id, + origin_url=origin_url, + timestamp=request.GET.get("timestamp"), + visit_id=request.GET.get("visit_id"), + branch_name=request.GET.get("branch"), + release_name=request.GET.get("release"), + revision_id=sha1_git, + ) per_page = int(request.GET.get("per_page", NB_LOG_ENTRIES)) offset = int(request.GET.get("offset", 0)) revs_ordering = request.GET.get("revs_ordering", "committer_date") @@ -284,7 +297,7 @@ "prev_log_url": prev_log_url, "breadcrumbs": None, "top_right_link": None, - "snapshot_context": None, + "snapshot_context": snapshot_context, "vault_cooking": None, "show_actions_menu": True, "swhids_info": None, @@ -308,22 +321,30 @@ revision = service.lookup_revision(sha1_git) origin_info = None snapshot_context = None - origin_url = request.GET.get("origin_url", None) + origin_url = request.GET.get("origin_url") if not origin_url: - origin_url = request.GET.get("origin", None) - timestamp = request.GET.get("timestamp", None) - visit_id = request.GET.get("visit_id", None) - snapshot_id = request.GET.get("snapshot_id", None) - path = request.GET.get("path", None) + origin_url = request.GET.get("origin") + timestamp = request.GET.get("timestamp") + visit_id = request.GET.get("visit_id") + snapshot_id = request.GET.get("snapshot_id") + if not snapshot_id: + snapshot_id = request.GET.get("snapshot") + path = request.GET.get("path") dir_id = None dirs, files = None, None content_data = {} if origin_url: try: snapshot_context = get_snapshot_context( - origin_url=origin_url, timestamp=timestamp, visit_id=visit_id + snapshot_id=snapshot_id, + origin_url=origin_url, + timestamp=timestamp, + visit_id=visit_id, + branch_name=request.GET.get("branch"), + release_name=request.GET.get("release"), + revision_id=request.GET.get("revision"), ) - except NotFoundExc: + except NotFoundExc as e: raw_rev_url = reverse( "browse-revision", url_args={"sha1_git": sha1_git} ) @@ -336,11 +357,15 @@ "without origin information: %s" % (gen_link(origin_url), gen_link(raw_rev_url)) ) - raise NotFoundExc(error_message) + if str(e).startswith("Origin"): + raise NotFoundExc(error_message) + else: + raise e origin_info = snapshot_context["origin_info"] snapshot_id = snapshot_context["snapshot_id"] elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) + if path: file_info = service.lookup_directory_with_path(revision["directory"], path) if file_info["type"] == "dir": @@ -399,12 +424,7 @@ path_info = gen_path_info(path) - query_params = { - "snapshot_id": snapshot_id, - "origin_url": origin_url, - "timestamp": timestamp, - "visit_id": visit_id, - } + query_params = snapshot_context["query_params"] if snapshot_context else {} breadcrumbs = [] breadcrumbs.append( @@ -466,17 +486,15 @@ content = content_display_data["content_data"] language = content_display_data["language"] mimetype = content_display_data["mimetype"] - query_params = {} if path: filename = path_info[-1]["name"] - query_params["filename"] = filename extra_context["filename"] = filename top_right_link = { "url": reverse( "browse-content-raw", url_args={"query_string": query_string}, - query_params=query_params, + query_params={"filename": filename}, ), "icon": swh_object_icons["content"], "text": "Raw File", @@ -527,14 +545,10 @@ swh_objects.append(SWHObjectInfo(object_type=DIRECTORY, object_id=dir_id)) + query_params.pop("path", None) + diff_revision_url = reverse( - "diff-revision", - url_args={"sha1_git": sha1_git}, - query_params={ - "origin_url": origin_url, - "timestamp": timestamp, - "visit_id": visit_id, - }, + "diff-revision", url_args={"sha1_git": sha1_git}, query_params=query_params, ) if snapshot_id: diff --git a/swh/web/browse/views/snapshot.py b/swh/web/browse/views/snapshot.py --- a/swh/web/browse/views/snapshot.py +++ b/swh/web/browse/views/snapshot.py @@ -51,7 +51,7 @@ request, snapshot_id=snapshot_id, path=request.GET.get("path"), - origin_url=request.GET.get("origin"), + origin_url=request.GET.get("origin_url"), ) diff --git a/swh/web/tests/browse/views/test_content.py b/swh/web/tests/browse/views/test_content.py --- a/swh/web/tests/browse/views/test_content.py +++ b/swh/web/tests/browse/views/test_content.py @@ -3,6 +3,7 @@ # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information +import random import textwrap from django.utils.html import escape @@ -10,6 +11,7 @@ from hypothesis import given from swh.model.identifiers import CONTENT, DIRECTORY +from swh.web.browse.snapshot_context import process_snapshot_branches from swh.web.browse.utils import ( get_mimetype_and_encoding_for_content, prepare_content_for_display, @@ -33,6 +35,7 @@ invalid_sha1, unknown_content, content_utf8_detected_as_binary, + origin_with_multiple_visits, ) @@ -420,6 +423,112 @@ assert_contains(resp, escape(content_display["content_data"])) +@given(origin_with_multiple_visits()) +def test_content_origin_snapshot_branch_browse(client, archive_data, origin): + visits = archive_data.origin_visit_get(origin["url"]) + visit = random.choice(visits) + snapshot = archive_data.snapshot_get(visit["snapshot"]) + branches, releases = process_snapshot_branches(snapshot) + branch_info = random.choice(branches) + + directory = archive_data.revision_get(branch_info["revision"])["directory"] + directory_content = archive_data.directory_ls(directory) + directory_file = random.choice( + [e for e in directory_content if e["type"] == "file"] + ) + + url = reverse( + "browse-content", + url_args={"query_string": directory_file["checksums"]["sha1"]}, + query_params={ + "origin_url": origin["url"], + "snapshot": snapshot["id"], + "branch": branch_info["name"], + "path": directory_file["name"], + }, + ) + + resp = client.get(url) + assert resp.status_code == 200 + assert_template_used(resp, "browse/content.html") + _check_origin_snapshot_related_html(resp, origin, snapshot, branches, releases) + assert_contains(resp, directory_file["name"]) + assert_contains(resp, f"Branch: {branch_info['name']}") + + +@given(origin_with_multiple_visits()) +def test_content_origin_snapshot_release_browse(client, archive_data, origin): + visits = archive_data.origin_visit_get(origin["url"]) + visit = random.choice(visits) + snapshot = archive_data.snapshot_get(visit["snapshot"]) + branches, releases = process_snapshot_branches(snapshot) + release_info = random.choice(releases) + + directory_content = archive_data.directory_ls(release_info["directory"]) + directory_file = random.choice( + [e for e in directory_content if e["type"] == "file"] + ) + + url = reverse( + "browse-content", + url_args={"query_string": directory_file["checksums"]["sha1"]}, + query_params={ + "origin_url": origin["url"], + "snapshot": snapshot["id"], + "release": release_info["name"], + "path": directory_file["name"], + }, + ) + + resp = client.get(url) + assert resp.status_code == 200 + assert_template_used(resp, "browse/content.html") + _check_origin_snapshot_related_html(resp, origin, snapshot, branches, releases) + assert_contains(resp, directory_file["name"]) + assert_contains(resp, f"Release: {release_info['name']}") + + +def _check_origin_snapshot_related_html(resp, origin, snapshot, branches, releases): + browse_origin_url = reverse( + "browse-origin", query_params={"origin_url": origin["url"]} + ) + assert_contains( + resp, + textwrap.indent( + ( + "Browse archived content for origin\n" + f'\n' + f" {origin['url']}\n" + f"" + ), + " " * 6, + ), + ) + + origin_branches_url = reverse( + "browse-origin-branches", + query_params={"origin_url": origin["url"], "snapshot": snapshot["id"]}, + ) + + assert_contains( + resp, + 'Branches (%s)' % (escape(origin_branches_url), len(branches)), + ) + + origin_releases_url = reverse( + "browse-origin-releases", + query_params={"origin_url": origin["url"], "snapshot": snapshot["id"]}, + ) + + assert_contains( + resp, + 'Releases (%s)' % (escape(origin_releases_url), len(releases)), + ) + + assert_contains(resp, '